﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using System.Globalization;
using System.Collections;

namespace tiDE
{
    public partial class Assembler
    {
        #region Variables and initialization
        private ushort CurrentOrigin;
        private AssemblyOutput Output;

        /// <summary>
        /// Include files saved between sessions
        /// </summary>
        public static Dictionary<string, SavedInclude> SavedIncludeFiles;

        private List<byte> OutputBytes;

        /// <summary>
        /// Constant values, such as PlotSScreen
        /// Name/value
        /// </summary>
        private Dictionary<string, Constant> Constants { get; set; }

        /// <summary>
        /// Static defines, such as
        /// #define TI83Plus
        /// </summary>
        private List<string> StaticDefines { get; set; }

        /// <summary>
        /// The location of labels in the output
        /// Name/Offset
        /// </summary>
        private Dictionary<string, ushort> Labels { get; set; }

        /// <summary>
        /// References to labels in the output
        /// Name/Offset
        /// </summary>
        private List<Reference> References { get; set; }

        /// <summary>
        /// Defines hardware operand tables
        /// </summary>
        public static Dictionary<string, HardwareOperand[]> OperandTable { get; set; }

        private List<string> PublicLabels { get; set; }

        public bool UseOrderOfOperations = true;

        List<Macro> Macros;

        public static List<OPCode> OPCodes { get; set; }
        private string Source { get; set; }
        /// <summary>
        /// Lines pass two should ignore
        /// </summary>
        List<int> PassTwoIgnore { get; set; }

        bool AccessModifiers = false;

        public AssemblyOutput Assemble(string Source, string SourceDirectory, string SourceFile)
        {
            if (SavedIncludeFiles == null)
                SavedIncludeFiles = new Dictionary<string, SavedInclude>();

            // Create the output
            Output = new AssemblyOutput();
            Source = Source.Replace("\r", "") + "\n"; // An extra newline to avoid certain errors

            this.SourceDirectory = SourceDirectory;

            // Initialize the one-time variables
            CurrentOrigin = 0x0000;
            Constants = new Dictionary<string, Constant>();
            Labels = new Dictionary<string, ushort>();
            References = new List<Reference>();
            LineNumbers = new List<int>();
            OutputBytes = new List<byte>();
            PublicLabels = new List<string>();
            Macros = new List<Macro>();
            StaticDefines = new List<string>();

            // Set the source code
            this.Source = Source;

            // Load the required tables and definitions
            LoadHardwareOperands();
            LoadOpcodes();

            // Table file changes
            // Repair sizing
            //string[][] immValues = new string[][]
            //    {
            //        new string[] {"%", "0000000000000000" },
            //        new string[] {"&", "00000000" },
            //        new string[] {"^", "00000000" },
            //        new string[] {"*", "00000000" },
            //        new string[] {"!", "000" },
            //        new string[] {"#", "000" },
            //    };

            //for (int i = 0; i < OPCodes.Count; i++ )
            //{
            //    OPCode workingCode = OPCodes[i];
            //    string value = workingCode.Value;
            //    int numHWOps = 0;
            //    foreach (string operand in OperandTable.Keys)
            //    {
            //        if (value.Contains(operand))
            //        {
            //            value = value.Replace(operand, OperandTable[operand][0].Value);
            //            numHWOps++;
            //        }
            //    }
            //    int numImmValues = 0;
            //    foreach (string[] immValue in immValues)
            //    {
            //        if (value.Contains(immValue[0]))
            //        {
            //            value = value.Replace(immValue[0], immValue[1]);
            //            numImmValues++;
            //        }
            //    }
            //    byte size = (byte)((value.Length - numImmValues - numHWOps) / 8);
            //    OPCodes[i].Size = size;
            //}
            //BinaryFormatter format = new BinaryFormatter();
            //Stream stream = File.Create("z80.tab");
            //format.Serialize(stream, OPCodes);
            //stream.Close();
            

            // Assemble the source
            PassOne();
            PassTwo();
            if (File.Exists("labelListing.txt"))
                File.Delete("labelListing.txt");
            StreamWriter writer = new StreamWriter("labelListing.txt");
            foreach (KeyValuePair<string, ushort> label in Labels)
            {
                writer.WriteLine(label.Value.ToString("x") + ": " + label.Key);
                Console.WriteLine(label.Value.ToString("x") + ": " + label.Key);
            }
            writer.Flush();
            writer.Close();

            Output.Output = OutputBytes.ToArray();

            // Remove all non-public labels (if applicable)
            if (AccessModifiers)
            {
                Dictionary<string, ushort> newLabels = new Dictionary<string, ushort>();
                foreach (string label in PublicLabels)
                {
                    if (Labels.ContainsKey(label))
                        newLabels.Add(label, Labels[label]);
                    else
                        AddError(new AssemblerError()
                        {
                            IndexOf = -1,
                            Line = -1,
                            Type = AssemblerErrorType.Undefined,
                            UserMessage = label + " does not exist.",
                        });
                }
                Labels = newLabels;
            }
            Output.Labels = Labels;
            Output.References = References;

            Output.File = SourceFile;
            Output.TimeAssembled = DateTime.Now;

            return Output;
        }

        private void LoadOpcodes()
        {
            if (OPCodes != null)
                return;
            BinaryFormatter formatter = new BinaryFormatter();
            Stream tableStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("tiDE.z80.tab");
            //Stream tableStream = File.Open("z80.tab", FileMode.Open);
            OPCodes = (List<OPCode>)formatter.Deserialize(tableStream);
            tableStream.Close();
        }

        bool noList = false, ignoreErrors = false, addNoRefs = false;

        string SourceDirectory;
        List<int> LineNumbers = new List<int>();
        #endregion

        #region Assembly

        bool enableDynamicBreakpoints = false;

        /// <summary>
        /// Collect all label values, parse macros, include files, etc
        /// </summary>
        private void PassOne()
        {
            int lineNumber = 0, actualLineNumber = 0, currentIndex = 0, includeFile = 0, ZDSMacroIndex = 0;
            ushort currentAddress = 0;
            PassTwoIgnore = new List<int>();
            Stack<SavedInclude> WorkingSavedIncludes = new Stack<SavedInclude>();
            while (currentIndex < Source.Length && Source.IndexOf('\n', currentIndex) != -1)
            {
                string _line = Source.Substring(currentIndex, Source.IndexOf('\n', currentIndex) - currentIndex);
                currentIndex += _line.Length + 1;
                actualLineNumber++;
                if (includeFile != 0)
                {
                    includeFile -= _line.Length + 1;
                    if (includeFile == 0)
                    {
                        WorkingSavedIncludes.Peek().TimeOfSave = DateTime.Now;
                        SavedIncludeFiles.Add(WorkingSavedIncludes.Peek().Name, WorkingSavedIncludes.Pop());
                    }
                }
                if (ZDSMacroIndex != 0)
                    ZDSMacroIndex -= _line.Length + 1;
                if (includeFile == 0 && ZDSMacroIndex == 0)
                    lineNumber++;
                LineNumbers.Add(lineNumber);
                string line = _line.Trim();
                line = InternalStripComments(line).Trim();
                string newLine = line;
                foreach (Macro macro in Macros)
                {
                    if (((macro.Arguments == null || macro.Arguments.Length == 0) && newLine == macro.Match) ||
                        (!(macro.Arguments == null || macro.Arguments.Length == 0) && newLine.StartsWith(macro.Match)))
                    {
                        // Yay, a macro!
                        newLine = newLine.Substring(macro.Match.Length);
                        if (macro.IsTasm)
                        {
                            if (macro.Arguments != null)
                            {
                                newLine = newLine.Substring(1);
                                newLine = newLine.Remove(newLine.Length - 1);
                                string[] args = newLine.Split(new char[] { ',' });
                                if (args.Length != macro.Arguments.Length)
                                    continue;
                                newLine = macro.Value;
                                for (int i = 0; i < macro.Arguments.Length; i++)
                                {
                                    try
                                    {
                                        newLine = newLine.Replace(macro.Arguments[i], args[i]);
                                    }
                                    catch { }
                                }
                            }
                            else
                            {
                                newLine = macro.Value;
                            }
                        }
                    }
                }
                string[] sublines = newLine.Split(new char[] { '\\' });
                if (newLine.Trim().StartsWith("#define") || newLine.Trim().StartsWith("define") ||
                    newLine.StartsWith(".macro") || newLine.StartsWith("macro"))
                    sublines = new string[] { newLine };
                foreach (string subline in sublines)
                {
                    line = subline.Trim();
                    if (!string.IsNullOrWhiteSpace(line)) // Ignore empty lines
                    {
                        if (line.StartsWith("."))
                            line = line.Substring(1);
                        if (line.StartsWith("#"))
                            line = line.Substring(1);
                        if (line.StartsWith("page "))
                        {
                            PassTwoIgnore.Add(actualLineNumber);
                            Output.Page = BytesFromBinary(ParseExpression(line.Substring(5), 8))[0];
                        }
                        if (line == "enableaccess")
                        {
                            AccessModifiers = true;
                            PassTwoIgnore.Add(actualLineNumber);
                        }
                        if (line.StartsWith("extern "))
                        {
                            AccessModifiers = true;
                            PassTwoIgnore.Add(actualLineNumber);
                        }
                        if (line.StartsWith("public "))
                        {
                            AccessModifiers = true;
                            PassTwoIgnore.Add(actualLineNumber);
                            foreach (string label in line.Substring(7).Split(new char[] { ',' }))
                                PublicLabels.Add(label.Trim().ToLower());
                        }
                        if (line == "nolist")
                        {
                            noList = true;
                            PassTwoIgnore.Add(actualLineNumber);
                        }
                        if (line == "list")
                        {
                            noList = false;
                            PassTwoIgnore.Add(actualLineNumber);
                        }
                        if (line.EndsWith("macro"))
                        {
                            // ZDS Macro
                            // TODO: Re-make this
                        }
                        if (line.StartsWith("define"))
                        {
                            PassTwoIgnore.Add(actualLineNumber);
                            // Tasm maco
                            // http://home.comcast.net/~tasm/tasmman.htm <- More TASM info
                            line = line.Substring(7).Trim();
                            if (!line.Contains(" "))
                            {
                                // Static define
                                StaticDefines.Add(line.Trim());
                                if (WorkingSavedIncludes.Count != 0)
                                    WorkingSavedIncludes.Peek().StaticDefines.Add(line.Trim());
                                continue;
                            }
                            Macro macro = new Macro();
                            macro.IsTasm = true;
                            bool hasArgs = false;
                            if (line.Contains("(") && line.Contains(")") &&
                                (line.IndexOf("(") - line.IndexOf(")")) != 0)
                            {
                                if (line.IndexOf("(") < line.IndexOf(" "))
                                {
                                    macro.Match = line.Remove(line.IndexOf("("));
                                    line = line.Substring(line.IndexOf("("));
                                    hasArgs = true;
                                }
                                else if (line.IndexOf("(") < line.IndexOf("\t"))
                                {
                                    macro.Match = line.Remove(line.IndexOf("("));
                                    line = line.Substring(line.IndexOf("("));
                                    hasArgs = true;
                                }
                                else // No arguments
                                {
                                    if (line.Contains(" "))
                                    {
                                        if (line.IndexOf(" ") < line.IndexOf("\t"))
                                        {
                                            macro.Match = line.Remove(line.IndexOf(" "));
                                            line = line.Substring(line.IndexOf(" "));
                                        }
                                        else
                                        {
                                            macro.Match = line.Remove(line.IndexOf("\t"));
                                            line = line.Substring(line.IndexOf("\t"));
                                        }
                                    }
                                    else
                                    {
                                        macro.Match = line.Remove(line.IndexOf("\t"));
                                        line = line.Substring(line.IndexOf("\t"));
                                    }
                                    if (!hasArgs)
                                        macro.Value = line.Trim();
                                }
                            }
                            else
                            {
                                if (line.Contains("\t"))
                                {
                                    if (line.IndexOf(" ") < line.IndexOf("\t"))
                                    {
                                        macro.Match = line.Remove(line.IndexOf(" ")).Trim();
                                        macro.Value = line.Substring(line.IndexOf(" ") + 1).Trim();
                                    }
                                    else
                                    {
                                        macro.Match = line.Remove(line.IndexOf("\t")).Trim();
                                        macro.Value = line.Substring(line.IndexOf("\t") + 1).Trim();
                                    }
                                }
                                else
                                {
                                    macro.Match = line.Remove(line.IndexOf(" ")).Trim();
                                    macro.Value = line.Substring(line.IndexOf(" ") + 1).Trim();
                                }
                            }
                            if (hasArgs)
                            {
                                string argString = line.Remove(line.IndexOf(")"));
                                argString = argString.Substring(1);
                                string[] args = argString.Split(new char[] { ',' });
                                line = line.Substring(line.IndexOf(")") + 1);
                                macro.Value = line;
                                macro.Arguments = args;
                            }
                            Macros.Add(macro);
                            if (WorkingSavedIncludes.Count != 0)
                                WorkingSavedIncludes.Peek().Macros.Add(macro);
                            continue;
                        }
                        if (SafeContains(line, "equ") || SafeContains(line, "=")) // TODO: fix for strings and non-define equ's
                        {
                            line = line.Replace("=", "equ").Replace(".equ", "equ");
                            ignoreErrors = true;
                            addNoRefs = true;
                            string equate = line.Remove(line.IndexOf("equ"));
                            string value = line.Substring(line.IndexOf("equ") + 3);
                            if (equate.EndsWith("."))
                                equate = equate.Remove(equate.Length - 1);
                            equate = equate.Trim();
                            value = value.Trim();
                            value = ParseExpression(value, -1);
                            if (!Constants.ContainsKey(equate))
                            {
                                Constants.Add(equate.ToLower(), new Constant()
                                {
                                    Value = value,
                                    Name = equate,
                                });
                                if (WorkingSavedIncludes.Count != 0)
                                    WorkingSavedIncludes.Peek().Constants.Add(equate.ToLower(), new Constant()
                                {
                                    Name = equate,
                                    Value = value,
                                });
                            }
                            PassTwoIgnore.Add(actualLineNumber);
                            ignoreErrors = false;
                            addNoRefs = false;
                        }
                        if (line.StartsWith("include"))
                        {
                            line = line.Substring(9);
                            line = line.Remove(line.Length - 1);
                            if (SavedIncludeFiles.ContainsKey(SourceDirectory + line) &&
                                SavedIncludeFiles[SourceDirectory + line].TimeOfSave >
                                File.GetLastWriteTime(SourceDirectory + line))
                            {
                                // We can use the saved include file information
                                Macros.AddRange(SavedIncludeFiles[SourceDirectory + line].Macros);
                                StaticDefines.AddRange(SavedIncludeFiles[SourceDirectory + line].StaticDefines);
                            }
                            else
                            {
                                if (SavedIncludeFiles.ContainsKey(SourceDirectory + line) &&
                                    SavedIncludeFiles[SourceDirectory + line].TimeOfSave <=
                                    File.GetLastWriteTime(SourceDirectory + line))
                                {
                                    SavedIncludeFiles.Remove(SourceDirectory + line);
                                }
                                if (!File.Exists(SourceDirectory + line))
                                {
                                    AddError(new AssemblerError()
                                    {
                                        Type = AssemblerErrorType.MissingFile,
                                        UserMessage = "File not found: " + line,
                                    });
                                    PassTwoIgnore.Add(actualLineNumber);
                                    continue;
                                }
                                StreamReader reader = new StreamReader(SourceDirectory + line);
                                string file = reader.ReadToEnd();
                                reader.Close();
                                includeFile = file.Length;
                                WorkingSavedIncludes.Push(new SavedInclude()
                                    {
                                        Name = SourceDirectory + line,
                                        StaticDefines = new List<string>(),
                                        Constants = new Dictionary<string, Constant>(),
                                        Macros = new List<Macro>(),
                                    });
                                Source = Source.Insert(currentIndex, file + "\n");
                                PassTwoIgnore.Add(actualLineNumber);
                                lineNumber -= 2;

                            }
                        }
                        if (line.StartsWith("org") && !noList)
                        {
                            line = line.Substring(3).Trim();
                            CurrentOrigin = UshortFromBinary(ParseExpression(line, 16, false, 0, false, false));
                            continue;
                        }
                        if (line.StartsWith("db") && !noList)
                        {
                            line = line.Substring(2).Trim();
                            string[] values = SplitLineOutsideSpecial(line, ',');
                            foreach (string value in values)
                            {
                                byte[] output;
                                if (value.Trim().StartsWith("\""))
                                {
                                    string stringValue = value.Substring(1, value.Length - 2);
                                    output = ASCIIEncoding.ASCII.GetBytes(stringValue);
                                    currentAddress += (ushort)output.Length;
                                    continue;
                                }
                                output = BytesFromBinary(ParseExpression(value.Trim(), 8));
                                currentAddress += (ushort)output.Length;
                            }
                            continue;
                        }
                        if (line.StartsWith("dw") && !noList)
                        {
                            line = line.Substring(2).Trim();
                            string[] values = line.Split(new char[] { ',' });
                            foreach (string value in values)
                            {
                                byte[] output;
                                if (value.Trim().StartsWith("\""))
                                {
                                    string stringValue = value.Substring(1, value.Length - 2);
                                    output = ASCIIEncoding.ASCII.GetBytes(stringValue);
                                    currentAddress += (ushort)output.Length;
                                    continue;
                                }
                                output = BytesFromBinary(ParseExpression(value.Trim(), 16));
                                currentAddress += (ushort)output.Length;
                            }
                            continue;
                        }
                        if (noList && !line.EndsWith(":"))
                            PassTwoIgnore.Add(actualLineNumber);
                        if (line.EndsWith(":") && !noList) // Label
                        {
                            PassTwoIgnore.Add(actualLineNumber);
                            if (!IsLabelValid(line))
                            {
                                AddError(new AssemblerError()
                                {
                                    Type = AssemblerErrorType.Syntax,
                                    UserMessage = "Invalid label name.  Labels must be alphanumeric and contain no whitespace.",
                                    IndexOf = 0,
                                    Line = lineNumber,
                                });
                                continue;
                            }
                            if (Labels.ContainsKey(line.Remove(line.LastIndexOf(':')).Trim().ToLower()))
                            {
                                AddError(new AssemblerError()
                                {
                                    Type = AssemblerErrorType.Duplicate,
                                    UserMessage = "Duplicate label found.",
                                    IndexOf = 0,
                                    Line = lineNumber,
                                });
                                continue;
                            }
                            Labels.Add(line.Remove(line.LastIndexOf(':')).Trim().ToLower(), (ushort)(currentAddress + CurrentOrigin));
                            continue;
                        }
                        // Check if it's an opcode
                        foreach (OPCode code in OPCodes)
                        {
                            if (OpcodeCompare(code.Match, line).IsMatch)
                            {
                                currentAddress += code.Size; // Code counter
                                break;
                            }
                        }
                    }
                }
            }
            if (includeFile != 0)
            {
                WorkingSavedIncludes.Peek().TimeOfSave = DateTime.Now;
                SavedIncludeFiles.Add(WorkingSavedIncludes.Peek().Name, WorkingSavedIncludes.Pop());
            }
        }

        public struct Constant
        {
            public string Name { get; set; }
            public string Value { get; set; }
        }

        private bool SafeContains(string line, string p)
        {
            bool withinQuotes = false;
            bool withinChars = false;
            int charsMatched = 0;
            for (int i = 0; i < line.Length; i++)
            {
                if (line[i] == '"')
                {
                    withinQuotes = !withinQuotes;
                    charsMatched = 0;
                    continue;
                }
                if (line[i] == '"')
                {
                    withinChars = !withinChars;
                    charsMatched = 0;
                    continue;
                }
                if (line[i] == p[charsMatched] && !withinQuotes && !withinChars)
                    charsMatched++;
                else
                    charsMatched = 0;
                if (charsMatched == p.Length)
                    return true;
            }
            return false;
        }

        /// <summary>
        /// Assemble opcodes into hex
        /// </summary>
        private void PassTwo()
        {
            passTwo = true;
            int lineNumber = 0;
            CurrentOrigin = 0;
            noList = false;
            foreach (string _line in Source.Replace("\r", "").Split(new char[] { '\n' }))
            {
                lineNumber++;
                string line = _line.Trim();
                line = InternalStripComments(line).Trim();
                if (!string.IsNullOrWhiteSpace(line))
                {
                    if (PassTwoIgnore.Contains(lineNumber))
                    {
                        PassTwoIgnore.Remove(lineNumber);
                        continue;
                    }
                    string newLine = line;
                    foreach (Macro macro in Macros)
                    {
                        if (newLine.StartsWith(macro.Match))
                        {
                            // Yay, a macro!
                            newLine = newLine.Substring(macro.Match.Length);
                            if (macro.IsTasm)
                            {
                                if (macro.Arguments != null)
                                {
                                    newLine = newLine.Substring(1);
                                    newLine = newLine.Remove(newLine.Length - 1); // Remove ()
                                }
                            }
                            if (macro.Arguments != null)
                            {
                                string[] args = newLine.Split(new char[] { ',' });
                                if (args.Length != macro.Arguments.Length)
                                    continue;
                                newLine = macro.Value;
                                for (int i = 0; i < macro.Arguments.Length; i++)
                                {
                                    newLine = newLine.Replace(macro.Arguments[i], args[i]);
                                }
                            }
                            else
                            {
                                newLine = macro.Value;
                            }
                        }
                    }
                    foreach (string subline in newLine.Split(new char[] { '\\' }))
                    {
                        line = subline.Trim();
                        if (!string.IsNullOrWhiteSpace(line)) // Ignore empty lines
                        {
                            if (line.StartsWith("."))
                                line = line.Substring(1);
                            if (line.StartsWith("org"))
                            {
                                line = line.Substring(3).Trim();
                                CurrentOrigin = UshortFromBinary(ParseExpression(line, 16, false, 0, false, false));
                                continue;
                            }
                            bool matchFound = false;
                            if (line.StartsWith("db"))
                            {
                                line = line.Substring(2).Trim();
                                string[] values = SplitLineOutsideSpecial(line, ',');
                                foreach (string value in values)
                                {
                                    byte[] output;
                                    if (value.Trim().StartsWith("\""))
                                    {
                                        string stringValue = value.Substring(1, value.Length - 2);
                                        output = ASCIIEncoding.ASCII.GetBytes(stringValue);
                                        OutputBytes.AddRange(output);
                                        continue;
                                    }
                                    output = BytesFromBinary(ParseExpression(value.Trim(), 8));
                                    OutputBytes.AddRange(output);
                                }
                                continue;
                            }
                            if (line.StartsWith("dw"))
                            {
                                line = line.Substring(2).Trim();
                                string[] values = line.Split(new char[] { ',' });
                                foreach (string value in values)
                                {
                                    byte[] output;
                                    if (value.Trim().StartsWith("\""))
                                    {
                                        string stringValue = value.Substring(1, value.Length - 2);
                                        output = ASCIIEncoding.ASCII.GetBytes(stringValue);
                                        OutputBytes.AddRange(output);
                                        continue;
                                    }
                                    output = BytesFromBinary(ParseExpression(value.Trim(), 16));
                                    OutputBytes.AddRange(output);
                                }
                                continue;
                            }
                            foreach (OPCode opcode in OPCodes)
                            {
                                OpcodeComparisonResult result = OpcodeCompare(opcode.Match, line);
                                if (result.IsMatch)
                                {
                                    string value = opcode.Value;
                                    bool continueAfterHW = false;

                                    if (result.HardwareTablesUsed.Length != 0) // Hardware operands
                                    {
                                        for (int index = 0; index < result.HardwareOperandIndicies.Length; index++)
                                        {
                                            string code = line.Substring(result.HardwareOperandIndicies[index],
                                                result.HardwareOperandLengths[index]);
                                            bool foundOperand = false;
                                            if (result.HardwareTablesUsed[index][0] == 'I')
                                            {
                                                // RST
                                                byte[] bytes = BytesFromBinary(ParseExpression(code, 8));
                                                code = "0123456789ABCDEF".Substring(bytes[0] / 16, 1) +
                                                    "0123456789ABCDEF".Substring(bytes[0] % 16, 1);
                                            }
                                            foreach (HardwareOperand op in OperandTable["$" + result.HardwareTablesUsed[index][0].ToString()])
                                            {
                                                if (op.Operand.ToLower() == code.ToLower())
                                                {
                                                    foundOperand = true;
                                                    value = value.Replace("$" + result.HardwareTablesUsed[index].ToString(),
                                                        op.Value);
                                                    break;
                                                }
                                            }
                                            if (!foundOperand)
                                            {
                                                //AddError(new AssemblerError()
                                                //    {
                                                //        Line = LineNumbers[lineNumber - 1],
                                                //        Type = AssemblerErrorType.InvalidOperand,
                                                //        UserMessage = "Invalid hardware operand: " + _line,
                                                //    });
                                                continueAfterHW = true;
                                                //matchFound = true;
                                                continue;
                                            }
                                        }
                                    }

                                    if (continueAfterHW)
                                        continue;

                                    if (result.ImmediateValueIndicies.Length != 0) // Immediate values
                                    {
                                        for (int index = 0; index < result.ImmediateValueIndicies.Length; index++)
                                        {
                                            string immValue = line.Substring(result.ImmediateValueIndicies[index],
                                                result.ImmediateValueLengths[index]);
                                            int numBits = 0;
                                            switch (result.ImmediateValueIdentifier[index][0])
                                            {
                                                case '%':
                                                    numBits = 16;
                                                    break;
                                                case '&':
                                                    numBits = 8;
                                                    break;
                                                case '!':
                                                    numBits = 3;
                                                    break;
                                                case '#':
                                                    numBits = 8;
                                                    break;
                                                default:
                                                    numBits = 8;
                                                    break;
                                            }
                                            value = value.Replace(result.ImmediateValueIdentifier[index],
                                                ParseExpression(immValue, numBits, result.ImmediateValueIdentifier[index][0] == '#',
                                                result.ImmediateValueIdentifier[index][0] == '#' ? 2 : 0,
                                                result.ImmediateValueIdentifier[index][0] == '^', true, value.IndexOf(result.ImmediateValueIdentifier[index]) / 8));
                                        }
                                    }
                                    byte[] additionalOutput = BytesFromBinary(value);
                                    OutputBytes.AddRange(additionalOutput);

                                    matchFound = true;
                                    break;
                                }
                            }
                            if (!matchFound)
                            {
                                AddError(new AssemblerError()
                                {
                                    Line = lineNumber - 1 < LineNumbers.Count ? LineNumbers[lineNumber - 1] : 0,
                                    IndexOf = -1,
                                    Type = AssemblerErrorType.InvalidOpcode,
                                    UserMessage = "Invalid opcode or macro: " + _line,
                                });
                            }
                        }
                    }
                }
            }
        }

        private string[] SplitLineOutsideSpecial(string str, char splitChar)
        {
            List<string> Sections = new List<string>();
            string current = "";
            bool isInString = false;
            foreach (char c in str)
            {
                if (c == '\"')
                {
                    isInString = !isInString;
                    current += "\"";
                    continue;
                }
                if (c == splitChar && !isInString)
                {
                    Sections.Add(current);
                    current = "";
                    continue;
                }
                current += c;
            }
            Sections.Add(current);
            return Sections.ToArray();
        }

        #endregion

        #region Hardware Operand Table

        /// <summary>
        /// Defines an operand and its value in binary, such as HL.
        /// </summary>
        public struct HardwareOperand
        {
            public string Operand, Value;

            public HardwareOperand(string Operand, string Value)
            {
                this.Operand = Operand;
                this.Value = Value;
            }
        }

        /// <summary>
        /// Loads the hardware operand constants
        /// </summary>
        private void LoadHardwareOperands()
        {
            if (OperandTable != null)
                return;

            OperandTable = new Dictionary<string, HardwareOperand[]>();
            HardwareOperand[] Condition2Bit = new HardwareOperand[4];
            Condition2Bit[0] = new HardwareOperand("NZ", "00");
            Condition2Bit[1] = new HardwareOperand("Z", "01");
            Condition2Bit[2] = new HardwareOperand("NC", "10");
            Condition2Bit[3] = new HardwareOperand("C", "11");
            OperandTable.Add("$A", Condition2Bit);

            HardwareOperand[] Condition3Bit = new HardwareOperand[8];
            Condition3Bit[0] = new HardwareOperand("NZ", "000");
            Condition3Bit[1] = new HardwareOperand("Z", "001");
            Condition3Bit[2] = new HardwareOperand("NC", "010");
            Condition3Bit[3] = new HardwareOperand("C", "011");
            Condition3Bit[4] = new HardwareOperand("PO", "100");
            Condition3Bit[5] = new HardwareOperand("PE", "101");
            Condition3Bit[6] = new HardwareOperand("P", "110");
            Condition3Bit[7] = new HardwareOperand("M", "111");
            OperandTable.Add("$B", Condition3Bit);

            HardwareOperand[] Register8Bit3Bit = new HardwareOperand[8];
            Register8Bit3Bit[0] = new HardwareOperand("B", "000");
            Register8Bit3Bit[1] = new HardwareOperand("C", "001");
            Register8Bit3Bit[2] = new HardwareOperand("D", "010");
            Register8Bit3Bit[3] = new HardwareOperand("E", "011");
            Register8Bit3Bit[4] = new HardwareOperand("H", "100");
            Register8Bit3Bit[5] = new HardwareOperand("L", "101");
            Register8Bit3Bit[6] = new HardwareOperand("(HL)", "110");
            Register8Bit3Bit[7] = new HardwareOperand("A", "111");
            OperandTable.Add("$C", Register8Bit3Bit);

            HardwareOperand[] Register16Bit1Bit = new HardwareOperand[2];
            Register16Bit1Bit[0] = new HardwareOperand("BC", "0");
            Register16Bit1Bit[1] = new HardwareOperand("DE", "1");
            OperandTable.Add("$D", Register16Bit1Bit);

            HardwareOperand[] Register8Bit1Bit = new HardwareOperand[2];
            Register8Bit1Bit[0] = new HardwareOperand("I", "0");
            Register8Bit1Bit[1] = new HardwareOperand("R", "1");
            OperandTable.Add("$E", Register8Bit1Bit);

            HardwareOperand[] Register16Bit2Bit = new HardwareOperand[4];
            Register16Bit2Bit[0] = new HardwareOperand("BC", "00");
            Register16Bit2Bit[1] = new HardwareOperand("DE", "01");
            Register16Bit2Bit[2] = new HardwareOperand("HL", "10");
            Register16Bit2Bit[3] = new HardwareOperand("SP", "11");
            OperandTable.Add("$F", Register16Bit2Bit);

            HardwareOperand[] Register16Bit2BitAlt = new HardwareOperand[4];
            Register16Bit2BitAlt[0] = new HardwareOperand("BC", "00");
            Register16Bit2BitAlt[1] = new HardwareOperand("DE", "01");
            Register16Bit2BitAlt[2] = new HardwareOperand("HL", "10");
            Register16Bit2BitAlt[3] = new HardwareOperand("AF", "11");
            OperandTable.Add("$G", Register16Bit2BitAlt);

            HardwareOperand[] IndexRegisters8Bit = new HardwareOperand[2];
            IndexRegisters8Bit[0] = new HardwareOperand("IX", "11011101");
            IndexRegisters8Bit[1] = new HardwareOperand("IY", "11111101");
            OperandTable.Add("$H", IndexRegisters8Bit);

            HardwareOperand[] RST3Bit = new HardwareOperand[8];
            RST3Bit[0] = new HardwareOperand("0", "000");
            RST3Bit[1] = new HardwareOperand("8", "001");
            RST3Bit[2] = new HardwareOperand("10", "010");
            RST3Bit[3] = new HardwareOperand("18", "011");
            RST3Bit[4] = new HardwareOperand("20", "100");
            RST3Bit[5] = new HardwareOperand("28", "101");
            RST3Bit[6] = new HardwareOperand("30", "110");
            RST3Bit[7] = new HardwareOperand("38", "111");
            OperandTable.Add("$I", RST3Bit);
        }

        #endregion
    }
    
    

    public class OutputTextEventArgs : EventArgs
    {
        public string Text { get; set; }

        public OutputTextEventArgs(string Text)
        {
            this.Text = Text;
        }
    }
}